// -----------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
// -----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.Internal;
using Microsoft.Internal.Collections;

namespace System.ComponentModel.Composition
{
    public partial class CompositionContainer : ExportProvider, ICompositionService, IDisposable
    {
        private CompositionEngine _compositionEngine;
        private MutableExportProvider _mutableExportProvider;
        private AggregatingExportProvider _aggregatingExportProvider;
        private AdaptingExportProvider _rootProvider;
        private ComposablePartCatalogExportProvider _catalogExportProvider;
        private IEnumerable<ExportProvider> _providers;
        private volatile bool _isDisposed = false;
        private Lock _lock = new Lock();
        private EventHandler<ExportsChangedEventArgs> _changedEventStorage;


        /// <summary>
        /// Initializes a new instance of the <see cref="CompositionContainer"/> class.
        /// </summary>
        public CompositionContainer()
            : this((ComposablePartCatalog)null)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="CompositionContainer"/> class.
        /// </summary>
        /// <param name="providers">The providers.</param>
        public CompositionContainer(params ExportProvider[] providers) : 
            this(null, providers)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="CompositionContainer"/> class.
        /// </summary>
        /// <param name="catalog">The catalog.</param>
        /// <param name="providers">The providers.</param>
        public CompositionContainer(ComposablePartCatalog catalog, params ExportProvider[] providers)
        {
            this._compositionEngine = new CompositionEngine();
            this._compositionEngine.SourceProvider = this;

            this._mutableExportProvider = new MutableExportProvider();
            this._mutableExportProvider.SourceProvider = this;

            if (catalog != null)
            {
                this._catalogExportProvider = new ComposablePartCatalogExportProvider(catalog);
                this._catalogExportProvider.SourceProvider = this;
            }

            this._providers = providers ?? Enumerable.Empty<ExportProvider>();

            this._rootProvider = new AdaptingExportProvider();
            this._rootProvider.ExportsChanged += this.OnExportsChangedInternal;

            providers = new ExportProvider[] { this._mutableExportProvider, this._catalogExportProvider }.Concat(this._providers).WhereNotNull().ToArray();

            // we only build the aggregating provider if necessary - that is, if we have more than one provider to aggregate
            if (providers.Length > 1)
            {
                this._aggregatingExportProvider = new AggregatingExportProvider(providers);
                this._rootProvider.SourceProvider = this._aggregatingExportProvider;
            }
            else
            {
                Assumes.IsTrue(providers.Length == 1);
                this._rootProvider.SourceProvider = providers[0];
            }
        }


        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (!this._isDisposed)
                {
                    bool disposeLock = false;
                    AdaptingExportProvider sourceProvider = null;
                    AggregatingExportProvider aggregatingExportProvider = null;
                    MutableExportProvider mutableExportProvider = null;
                    ComposablePartCatalogExportProvider catalogExportProvider = null;
                    CompositionEngine compositionEngine = null;
                    using (new WriteLock(this._lock))
                    {
                        if (!this._isDisposed)
                        {
                            sourceProvider = this._rootProvider;
                            aggregatingExportProvider = this._aggregatingExportProvider;
                            mutableExportProvider = this._mutableExportProvider;
                            catalogExportProvider = this._catalogExportProvider;
                            compositionEngine = this._compositionEngine;

                            this._rootProvider = null;
                            this._mutableExportProvider = null;
                            this._aggregatingExportProvider = null;
                            this._catalogExportProvider = null;
                            this._compositionEngine = null;

                            this._isDisposed = true;
                            disposeLock = true;
                        }
                    }

                    if (sourceProvider != null)
                    {
                        sourceProvider.ExportsChanged -= this.OnExportsChangedInternal;
                        sourceProvider.Dispose();
                    }

                    if (aggregatingExportProvider != null)
                    {
                        aggregatingExportProvider.Dispose();
                    }

                    if (mutableExportProvider != null)
                    {
                        mutableExportProvider.Dispose();
                    }

                    if (catalogExportProvider != null)
                    {
                        catalogExportProvider.Dispose();
                    }

                    if (compositionEngine != null)
                    {
                        compositionEngine.Dispose();
                    }

                    if (disposeLock)
                    {
                        this._lock.Dispose();
                    }
                }
            }
        }

        /// <summary>
        /// Gets the providers.
        /// </summary>
        /// <value>The providers.</value>
        public IEnumerable<ExportProvider> Providers
        {
            get
            {
                this.ThrowIfDisposed();
                return this._providers;
            }
        }


        /// <summary>
        ///     Gets a value indicating whether the container is currently composing.
        /// </summary>
        /// <value>
        ///     <see langword="true"/> if the <see cref="CompositionContainer"/> is currently 
        ///     composing due to a call to <see cref="Compose"/>, otherwise, <see langword="false"/>.
        /// </value>
        public bool IsComposing
        {
            get
            {
                this.ThrowIfDisposed();
                return this._mutableExportProvider.IsComposing;
            }
        }

        /// <summary>
        ///     Gets a value indicating whether the container has a composition pending.
        /// </summary>
        /// <value>
        ///     <see langword="true"/> if the <see cref="CompositionContainer"/> currently has
        ///     exported objects and <see cref="ComposablePart"/> objects that have not yet been
        ///     composed by a call to <see cref="Compose"/>; otherwise, <see langword="false"/>.
        /// </value>
        public bool IsCompositionPending
        {
            get 
            {
                this.ThrowIfDisposed();
                return this._mutableExportProvider.IsCompositionPending;
            }
        }


        /// <summary>
        ///     Occurs when the exports in the <see cref="ExportProvider"/> change.
        /// </summary>
        /// <remarks>
        ///     The <see cref="ExportsChanged" /> event passes an <see cref="ExportsChangedEventArgs" />
        ///     which provides a list of contract names for the exports that have changed. An export
        ///     is considered to have changed if it has been added, removed, or updated in the 
        ///     <see cref="AggregatingExportProvider" />.
        /// </remarks>
        public override event EventHandler<ExportsChangedEventArgs> ExportsChanged
        {
            add
            {
                using (new WriteLock(this._lock))
                {
                    this._changedEventStorage += value;
                }
            }
            remove
            {
                using (new WriteLock(this._lock))
                {
                    this._changedEventStorage -= value;
                }
            }
        }


        /// <summary>
        ///     Adds the specified attributed composable part to the container.
        /// </summary>
        /// <param name="attributedPart">
        ///     An <see cref="Object"/> containing the attributed composable part to add to the 
        ///     <see cref="CompositionContainer"/>.
        /// </param>
        /// <returns>
        ///     A <see cref="ComposablePart"/> that can be used remove the composable part from
        ///     the <see cref="CompositionContainer"/>.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="attributedPart"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ObjectDisposedException">
        ///     The <see cref="CompositionContainer"/> has been disposed of.
        /// </exception>
        /// <remarks>
        ///     <para>
        ///         This method is equivalent to passing <paramref name="attributedPart"/> to 
        ///         <see cref="CompositionServices.CreateAttributedPart"/> and passing the created 
        ///         instance to <see cref="AddPart(ComposablePart)"/>. 
        ///     </para>
        ///     <para>
        ///         If <paramref name="attributedPart"/> is already in the 
        ///         <see cref="CompositionContainer"/> then this method does nothing.
        ///     </para>
        ///     <para>
        ///         The composable part is not composed, nor are its exports available until
        ///         <see cref="Compose"/> is called.
        ///     </para>
        /// </remarks>
        public ComposablePart AddPart(object attributedPart)
        {
            Requires.NotNull(attributedPart, "attributedPart");
            this.ThrowIfDisposed();

            ComposablePart part = this.CreatePart(attributedPart);
            this.AddPart(part);
            return part;
        }

        /// <summary>
        ///     Adds the specified composable part to the container.
        /// </summary>
        /// <param name="part">
        ///     The <see cref="ComposablePart"/> to add to the <see cref="CompositionContainer"/>.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="part"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ObjectDisposedException">
        ///     The <see cref="CompositionContainer"/> has been disposed of.
        /// </exception>
        /// <remarks>
        ///     <para>
        ///         If <paramref name="part"/> is already in the <see cref="CompositionContainer"/> 
        ///         then this method does nothing.
        ///     </para>
        ///     <para>
        ///         The <see cref="ComposablePart"/> is not composed, nor are its exports available 
        ///         until <see cref="Compose"/> is called.
        ///     </para>
        /// </remarks>
        public void AddPart(ComposablePart part)
        {
            this.ThrowIfDisposed();
            this._mutableExportProvider.AddPart(part);
        }

        /// <summary>
        ///     Composes the container.
        /// </summary>
        /// <exception cref="CompositionException">
        ///     An error occurred during composition. <see cref="CompositionException.Issues"/> will
        ///     contain a list of errors that occurred.
        /// </exception>
        /// <exception cref="ObjectDisposedException">
        ///     The <see cref="CompositionContainer"/> has been disposed of.
        /// </exception>
        /// <exception cref="InvalidOperationException">
        ///     <see cref="Compose"/> was called again before composition had finished.
        /// </exception>
        /// <remarks>
        ///     <para>
        ///         You can use the <see cref="IsComposing"/> property to determine if the
        ///         <see cref="CompositionContainer"/> is in the process of composing.
        ///     </para>
        /// </remarks>
        public void Compose()
        {
            this.ThrowIfDisposed();
            this._mutableExportProvider.Compose();
        }

        

        /// <summary>
        ///     Removes the specified composable part from the container.
        /// </summary>
        /// <param name="part">
        ///     The <see cref="ComposablePart"/> to remove from the 
        ///     <see cref="CompositionContainer"/>.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="part"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ObjectDisposedException">
        ///     The <see cref="CompositionContainer"/> has been disposed of.
        /// </exception>
        /// <remarks>
        ///     <para>
        ///         If <paramref name="part"/> is not in the <see cref="CompositionContainer"/> then
        ///         this method does nothing.
        ///     </para>
        ///     <para>
        ///         The <see cref="ComposablePart"/> is not removed from the 
        ///         <see cref="CompositionContainer"/> and its exports are still available until 
        ///         <see cref="Compose"/> is called.
        ///     </para>
        /// </remarks>
        public void RemovePart(ComposablePart part)
        {
            this.ThrowIfDisposed();
            this._mutableExportProvider.RemovePart(part);
        }

        /// <summary>
        ///     Creates a composable part for the specified object.
        /// </summary>
        /// <param name="part">
        ///     An <see cref="Object"/> containing the object to create a <see cref="ComposablePart"/> 
        ///     for.
        /// </param>
        /// <returns>
        ///     The created <see cref="ComposablePart"/>.
        /// </returns>
        /// <remarks>
        ///     <para>
        ///         The default implementation of this method returns an instance of the
        ///         default attributed composable part.
        ///     </para>
        ///     <para>
        ///         This method can be overridden by a derived class. Implementers can override 
        ///         <see cref="CreatePart"/> to provide a custom implementation of 
        ///         <see cref="ComposablePart"/> for the <see cref="AddPart(object)"/> method.
        ///     </para>
        /// </remarks>
        protected virtual ComposablePart CreatePart(object part)
        {
            return CompositionServices.CreateAttributedPart(part);
        }

        internal void OnExportsChangedInternal(object sender, ExportsChangedEventArgs e)
        {
            CompositionResult result = CompositionServices.TryInvoke(() => this.OnExportsChanged(e));
            result.ThrowOnErrors();
        }


        /// <summary>
        ///     Raises the <see cref="ExportsChanged"/> event.
        /// </summary>
        /// <param name="e">
        ///     An <see cref="ExportsChangedEventArgs"/> containing the event data.
        /// </param>
        protected override void OnExportsChanged(ExportsChangedEventArgs e)
        {
            EventHandler<ExportsChangedEventArgs> changedEvent = null;

            using (new ReadLock(this._lock))
            {
                changedEvent = this._changedEventStorage;
            }

            if (changedEvent != null)
            {
                CompositionResult result = CompositionServices.TryFire(changedEvent, this, e);
                result.ThrowOnErrors();
            }
        }

        /// <summary>
        /// Returns all exports that match the conditions of the specified import.
        /// </summary>
        /// <param name="importDefinition">The <see cref="ImportDefinition"/> that defines the conditions of the
        /// <see cref="Export"/> to get.</param>
        /// <returns></returns>
        /// <result>
        /// An <see cref="IEnumerable{T}"/> of <see cref="Export"/> objects that match
        /// the conditions defined by <see cref="ImportDefinition"/>, if found; otherwise, an
        /// empty <see cref="IEnumerable{T}"/>.
        /// </result>
        /// <remarks>
        /// 	<note type="inheritinfo">
        /// The implementers should not treat the cardinality-related mismatches as errors, and are not
        /// expected to throw exceptions in those cases.
        /// For instance, if the import requests exactly one export and the provider has no matching exports or more than one,
        /// it should return an empty <see cref="IEnumerable{T}"/> of <see cref="Export"/>.
        /// </note>
        /// </remarks>
        protected override IEnumerable<Export> GetExportsCore(ImportDefinition importDefinition)
        {
            this.ThrowIfDisposed();

            return this._rootProvider.GetExports(importDefinition);
        }


        public CompositionResult SatisfyImports(ComposablePart composablePart)
        {
            Requires.NotNull(composablePart, "composablePart");

            this.ThrowIfDisposed();
            return this._compositionEngine.SatisfyImportsAndRegisterForRecomposition(composablePart);
        }

        [DebuggerStepThrough]
        private void ThrowIfDisposed()
        {
            if (this._isDisposed)
            {
                throw ExceptionBuilder.CreateObjectDisposed(this);
            }
        }
    }
}
